#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>

#define DEFAULT_ADDRESS "127.0.0.1"
#define DEFAULT_PORT	4444
#define MAX_BUFF_SIZE   1500
#define MAX_CMD_SIZE 128

#define MC_LOW_ADD        0xe00000ff
#define	MC_HIGH_ADD       0xefffffff

#define OK 0
#define ERROR -1

static pthread_t ThreadId;
static int ThreadLoop = 1;
static void * ServerThread(void * Arg);
static char * InetAddr = INADDR_ANY;
static char * OwnInetAddr = INADDR_ANY;
static int InetPort = DEFAULT_PORT;
static int LocalSock = 0;
static unsigned int RecvByteCount = 0;

int main(int argc, char *argv[])
{
    int c = 0;
    opterr = 0;
    char Command[MAX_CMD_SIZE];

    // Parse option
     while ((c = getopt (argc, argv, "a:b:p:h")) != -1)
     {
 		switch (c)
 		{
		case 'a':
			if (optarg) InetAddr = optarg;
		break;

		case 'b':
			if (optarg) OwnInetAddr = optarg;
		break;

 		case 'p':
 			if (optarg) InetPort = atoi(optarg);
 		break;

 		case 'h':
 			printf("\n"
 				  "Server [options]\n"
 				  "\tOptions:\n"
				  "\t\t-a  address       internet address, dotted-string"
				  "\t\t-b  address       Our own internet address"
 				  "\t\t-p  port          port number");
 		break;

 		default:
 			break;
 		}
     }

 	printf("Start up UDP server\n");

	// Start server thread
	if (pthread_create(&ThreadId, NULL, ServerThread, (void *) &InetPort) != 0)
	{
		return -1;
	}

	// CLI process
	while (1)
	{
		// Get the command
		printf("Command: ");
		scanf ("%s", Command);

		// Parse the command
		if (strcmp("quit", Command) == 0)
		{
			break;
		}

		if (strcmp("help", Command) == 0)
		{
			printf(	"help - to get the list of internal command\n"
					"quit - to quit this CLI without closing the server\n"
					);
			continue;
		}
	}

	ThreadLoop = 0;
	shutdown(LocalSock, SHUT_RDWR);

	pthread_join(ThreadId, NULL);

	printf("Total Rx bytes: %i\n", RecvByteCount);
	printf("Bye bye!\n");

	return 0;
}

static void * ServerThread(void * Arg)
{
	int Error = 0;
	struct sockaddr_in LocalAddr;
	struct sockaddr_in ClientAddr;
    unsigned char Buffer[MAX_BUFF_SIZE];
    unsigned int opt = 1;
    struct ip_mreq mreq;


    // Create socket
	LocalSock = socket(AF_INET, SOCK_DGRAM, 0);
    if (LocalSock < 0)
    {
    	Error = errno;
    	pthread_exit(NULL);
    }

	if (setsockopt(LocalSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
	{
    	Error = errno;
	   	pthread_exit(NULL);
	}

    // Bind
    bzero((char *) &LocalAddr, sizeof(LocalAddr));
    LocalAddr.sin_family = AF_INET;
    LocalAddr.sin_addr.s_addr = inet_addr(InetAddr);
    LocalAddr.sin_port = htons(InetPort);
    if (bind(LocalSock, (struct sockaddr *) &LocalAddr, sizeof(LocalAddr)) < 0)
    {
    	Error = errno;
    	pthread_exit(NULL);
    }

	// Join multicast
    unsigned int MCAddr = ntohl(inet_addr(InetAddr));
    if ((MCAddr >= MC_LOW_ADD) && (MCAddr <= MC_HIGH_ADD))
    {
        mreq.imr_multiaddr.s_addr = inet_addr(InetAddr);
        mreq.imr_interface.s_addr = inet_addr(OwnInetAddr);
    	setsockopt(LocalSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
    }

	printf("Waiting packets on %s:%d...\n", InetAddr, InetPort);

    while(ThreadLoop)
	{
		int Len = 0;
	    int RecvCount = 1;

		bzero((void *)Buffer, MAX_BUFF_SIZE);
		Len = sizeof(struct sockaddr_in);
		RecvCount = recvfrom(LocalSock, (void *)Buffer, MAX_BUFF_SIZE, 0, (struct sockaddr*) &ClientAddr, (socklen_t *) &Len);
		if (RecvCount > 0)
		{
			RecvByteCount += RecvCount;
			//printf("Received %d bytes!\n", RecvCount);
		}
	}

    close(LocalSock);

    pthread_exit(NULL);
}

